package NG::Sys::Sock;
use strict;
use warnings;
use utf8;

require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(
InetListen
InetConnect
UnixListen
UnixConnect
CreateListener
SendMessage
WaitMessage
PrepareSendMessage
SendMessagePrepared
WaitMessagePrepared
PrepareWaitMessage
);

use IO::Socket::UNIX;
use IO::Socket::INET;
use EV;

our $DEBUG;
BEGIN{
	$SIG{'PIPE'} = 'IGNORE';
}
sub InetListen {
	my ($addr,$port) = @_;
	my $sock = new IO::Socket::INET(
			Listen => 100,
			LocalHost =>  $addr,
			LocalPort =>  $port,
			ReuseAddr => 1,
			#Blocking => 0,
		);
	unless($sock){
		die "Create inet sock : '$@' '$!'\n";
	}
	return $sock;
}
sub InetConnect {
	my ($addr,$port,$nodie) = @_;
	my $sock = new IO::Socket::INET(
			PeerHost =>  $addr,
			PeerPort =>  $port,
			#Blocking => 0,
		);
	unless($sock){
		die "Create inet sock : '$@' '$!'\n" unless $nodie;
	}
	return $sock;
}

sub UnixListen {
	my ($path) = @_;
	unlink $path;
	my $sock = new IO::Socket::UNIX(
			Listen => 100,
			Local =>  $path,
			#ReuseAddr => 1,
			#Blocking => 0,
		);
	unless($sock){
		die "Create unix sock : '$@' '$!'\n";
	}
	return $sock;
}
sub UnixConnect {
	my ($path,$nodie) = @_;
	my $sock = new IO::Socket::UNIX(
			Peer =>  $path,
			#Blocking => 0,
		);
	unless($sock){
		die "Create unix sock : '$@' '$!'\n" unless $nodie;
	}
	return $sock;
}
sub CreateListener {
	my ($sock,$on_connect,$parser,$on_message,$on_close,$on_error) = @_;
	my $reader = sub {
		my ($w, $r) = @_;
		my $buf;
		my $fh = $w->fh;
		if($fh->opened && $fh->connected){
			$fh->recv($buf,1024);
		}
		if($buf){
			if($DEBUG){
				(my $s = $buf) =~ s/\0/(0x0)/g;
				$DEBUG->info("\nreaded\n".$s."\n");
			}
			# есть данные, цепляем к предыдущим если есть и пытаемся обработать
			my $ref_str = \$w->data->[0];
			$$ref_str .= $buf;
			while($$ref_str){
				if(my $message = $parser->($ref_str)){
					$on_message->($w,$message);
				}else{
					return 0;
				}
			}
		}else{
			$on_close->($w);
		}
	};
	my $accepter = sub {
		my ($w, $r) = @_;
		my $new = $w->fh->accept;
		unless($new){
			$on_error->($w,"Cannot accept '$!' '$@'\n");
			return 0;
		}
		if($DEBUG){
			my $addr;
			if($w->fh->sockdomain == AF_INET){
				$addr = $new->peerhost.":".$new->peerport;
			}elsif($w->fh->sockdomain == AF_UNIX){
				$addr = $new->peerpath;
			}else{
				$addr ="NA(".$w->fh->sockdomain.")inet(".AF_INET.")unix(".AF_UNIX.")";
			}
			$DEBUG->info("\naccepted from: ".$addr."\n");
		}
		#$new->blocking(0);
		my $nw = EV::io $new, EV::READ, $reader;
		$nw->data([""]);
		$on_connect->($nw);
	};
	return EV::io $sock, EV::READ, $accepter;
}
sub SendMessage {
	my ($fh,$message,$on_close,$on_done) = @_;

	my $writer = sub {
		my ($w, $m) = @_;
		my $fh = $w->fh;
		if(my $str = $w->data->[0]){
			my $need = length($str);
			my $rs;
			if($fh->opened && $fh->connected){
				$rs = $fh->send($str);
			}
			if($rs){
				if($DEBUG){
					my $s = substr($w->data->[0],0,$rs);
					$s =~ s/\0/(0x0)/g;
					$DEBUG->info("\nsended\n".$s."\n");
				}
				$need -= $rs;
				# если отправлено не все, то продолжаем ждать
				if($need > 0){
					substr($w->data->[0],0,$rs,"");
					return 0;
				}else{
					$w->data->[0] = "";
				}
			}else{
				$on_close->($w);
			}
		}
		$on_done->($w);
	};
	my $nw = EV::io $fh, EV::WRITE, $writer;
	$nw->data([$message]);
	return $nw;
}
sub SendMessagePrepared {
	my ($fh,$message,$writer) = @_;
	my $nw = EV::io $fh, EV::WRITE, $writer;
	$nw->data([$message]);
	return $nw;
}
sub PrepareSendMessage {
	my ($on_close,$on_done) = @_;

	my $writer = sub {
		my ($w, $m) = @_;
		if(my $str = $w->data->[0]){
			my $fh = $w->fh;
			my $need = length($str);
			my $rs;
			if($fh->opened && $fh->connected){
				$rs = $fh->send($str);
			}
			if($rs){
				if($DEBUG){
					my $s = substr($w->data->[0],0,$rs);
					$s =~ s/\0/(0x0)/g;
					$DEBUG->info("\nsended\n".$s."\n");
				}
				$need -= $rs;
				# если отправлено не все, то продолжаем ждать
				if($need > 0){
					substr($w->data->[0],0,$rs,"");
					return 0;
				}else{
					$w->data->[0] = "";
					$on_done->($w);
				}
			}else{
				$on_close->($w);
			}
		}else{
			if($DEBUG){
				$DEBUG->error("\ntry send none for ".$w->data->[1]." fh: ".$w->fh->fileno."\n");
			}
		}
		#$on_done->($w);
	};
	return $writer;
}
sub WaitMessage {
	my ($sock,$parser,$on_message,$on_close) = @_;
	my $reader = sub {
		my ($w, $r) = @_;
		my $buf;
		my $fh = $w->fh;
		if($fh->opened && $fh->connected){
			$fh->recv($buf,1024);
		}
		if($buf){
			if($DEBUG){
				(my $s = $buf) =~ s/\0/(0x0)/g;
				$DEBUG->info("\nwaited\n".$s."\n");
			}
			# есть данные, цепляем к предыдущим если есть и пытаемся обработать
			my $ref_str = \$w->data->[0];
			$$ref_str .= $buf;
			while($$ref_str){
				if(my $message = $parser->($ref_str)){
					$on_message->($w,$message);
				}else{
					return 0;
				}
			}
		}else{
			$on_close->($w);
		}
	};
	my $nw = EV::io $sock, EV::READ, $reader;
	$nw->data([""]);
	return $nw;
}
sub WaitMessagePrepared {
	my ($sock,$reader) = @_;
	my $nw = EV::io $sock, EV::READ, $reader;
	$nw->data([""]);
	return $nw;
}
sub PrepareWaitMessage {
	my ($parser,$on_message,$on_close) = @_;
	my $reader = sub {
		my ($w, $r) = @_;
		my $buf;
		my $fh = $w->fh;
		if($fh->opened && $fh->connected){
			$fh->recv($buf,1024);
		}
		if($buf){
			if($DEBUG){
				(my $s = $buf) =~ s/\0/(0x0)/g;
				$DEBUG->info("\nwaited\n".$s."\n");
			}
			# есть данные, цепляем к предыдущим если есть и пытаемся обработать
			my $ref_str = \$w->data->[0];
			$$ref_str .= $buf;
			while($$ref_str){
				if(my $message = $parser->($ref_str)){
					$on_message->($w,$message);
				}else{
					return 0;
				}
			}
		}else{
			$on_close->($w);
		}
	};
	return $reader;
}

1;
